﻿/*
 * Created by SharpDevelop.
 * User: Corrado Mio
 * Date: 31/05/2012
 * Time: 06:33
 * 
 * Oggetto simile a Timer ma che utilizza un
 * singolo thread per l'esecuzione e la schedulazione. In questo
 * modo l'esecuzione della callback e' sincrona con la schedulazione
 */
using System;
using System.Threading;
using log4net;

namespace CSharpExt.Threading
{

    public class SyncTimer : IDisposable
    {
        #region Private Data
        
        private static ThreadLocal<SyncTimer> _currentTimer = new ThreadLocal<SyncTimer>();

        private int _dueTime;
        private int _period;
        private int _start;
        private Object _state;
        private Action<Object> _callback;
        private Thread _thread;
        private volatile bool _terminate;
        
        private volatile bool _suspended;

        #endregion

        #region Constructor
        
        // -- Action()

        public SyncTimer(Action callback, int dueTime, int period)
            : this(null, callback, dueTime, period)
        {

        }
        
        
        public SyncTimer(String name, Action callback, int dueTime, int period)
        	: this(name, (arg)=> { callback(); }, null, dueTime, period)
        {

        }
        
        
        // -- Action(Object)
        
        public SyncTimer(Action<Object> callback, Object state, int dueTime, int period)
            : this(null, callback, state, dueTime, period)
        {
            
        }

        public SyncTimer(String name, Action<Object> callback, Object state, int dueTime, int period)
        {
            _callback = callback;
            _state = state;
            _dueTime = dueTime;
            _period = period;
            _terminate = false;

            _thread = new Thread(call){ Name = name };
            
            _thread.Start();
        }

        
        // -- Runnable
        
        public SyncTimer(Runnable runnable, int dueTime, int period)
            : this(null, runnable, dueTime, period)
        {
            
        }

        public SyncTimer(String name, Runnable runnable, int dueTime, int period)
        	: this(name, callRunnable, runnable, dueTime, period)
        {
            
        }

        
        #endregion

        #region Operations
        
        public static SyncTimer CurrentTimer
        {
            get { return _currentTimer.Value; }
        }
        
        public void Suspend()
        {
            _suspended = true;
        }
        
        public void Resume()
        {
            _suspended = false;
        }

        public void Dispose()
        {
            _terminate = true;
            if (2*_period < 1000)
            {
                if (!_thread.Join(2 * _period))
                    _thread.Abort();
            }
            else
            {
                _thread.Abort();
            }
        }

        #endregion

        #region Implementation

        private void call()
        {
            _currentTimer.Value = this;
            
            if (_dueTime > 0)
                Thread.Sleep(_dueTime);

            while (!_terminate)
            {
                start();
            
               try
                {
                    if (!_suspended)
                        _callback(_state);
                }
                catch (ThreadAbortException)
                {
                    _terminate = true;
                    
                    LogManager.GetLogger(typeof(SyncTimer)).Error("Thread Aborted");
                }
                catch (Exception e)
                {
                    LogManager.GetLogger(typeof(SyncTimer)).Error(e, e);
                }

                sleep();
            }
        }
        
        private static void callRunnable(Object arg)
        {
        	((Runnable)arg).Run();
        }
        
        private void start()
        {
            _start = Environment.TickCount;
        }
        
        private void sleep()
        {
            if (!_terminate)
            {
                int rest = _period - (Environment.TickCount - _start);
                if (rest > 16)
                    Thread.Sleep(rest);
            }
        }

        #endregion
    }
}
